package com.jhh.pay.center.cfg.tablesplit;

import com.jhh.pay.center.cfg.SpringAppContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMap;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @author tangxd
 * @Description: TODO
 * @date 2017/12/5
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
@Slf4j
public class TableSplitInterceptor implements Interceptor {
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
    private static Properties props = null;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);

        Object parameterObject = metaStatementHandler.getValue("delegate.boundSql.parameterObject");
        doSplitTable(metaStatementHandler, parameterObject);
        // 传递给下一个拦截器处理
        return invocation.proceed();

    }

    @Override
    public Object plugin(Object target) {
        // 当目标类是StatementHandler类型时，才包装目标类，否者直接返回目标本身,减少目标被代理的次数
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public synchronized void setProperties(Properties properties) {
        if (props == null) {
            props = properties;
        }
    }

    private Strategy tableSplitStrategy(String name) {
        String beanName = "strategy." + name;
        if (props.containsKey(beanName)) {
            return (Strategy) SpringAppContext.getBean(props.getProperty(beanName));
        }
        return null;
    }

    private void doSplitTable(MetaObject metaStatementHandler, Object param) throws Exception {
        String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        if (originalSql != null && !originalSql.equals("")) {
            MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
            String id = mappedStatement.getId();
            String className = id.substring(0, id.lastIndexOf("."));
            String methodName = id.substring(id.lastIndexOf(".") + 1);
            Class<?> clazz = Class.forName(className);
            ParameterMap paramMap = mappedStatement.getParameterMap();
            Method method = findMethod(clazz.getDeclaredMethods(), methodName);
            // 根据配置自动生成分表SQL
            TableSplit tableSplit = null;
            if (method != null) {
                tableSplit = method.getAnnotation(TableSplit.class);
            }

            if (tableSplit == null) {
                tableSplit = clazz.getAnnotation(TableSplit.class);
            }
            if (tableSplit != null && tableSplit.split() && StringUtils.isNotBlank(tableSplit.strategy())) {
                String convertedSql = "";
                String[] strategies = tableSplit.strategy().split(",");
                for (String str : strategies) {
                    Strategy strategy = tableSplitStrategy(str);
                    Map<String, Object> params = new HashMap<>();
                    params.put(Strategy.TABLE_NAME, tableSplit.value());
                    params.put(Strategy.SPLIT_FIELD, tableSplit.field());
                    params.put(Strategy.EXECUTE_PARAM_DECLARE, paramMap);
                    params.put(Strategy.EXECUTE_PARAM_VALUES, param);

                    convertedSql = originalSql.replaceAll(tableSplit.value(), strategy.getTable(params));
                }
                metaStatementHandler.setValue("delegate.boundSql.sql", convertedSql);
            }
        }
    }

    private Method findMethod(Method[] methods, String methodName) {
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                return method;
            }
        }
        return null;
    }
}
